/**
 * @brief 从C++出发调用js中的接口。方便从其他的脚本中访问js中的内容
 * @version 1.0
 * @author 宋炜
 */

#pragma once

#include <map>
#include <string>
#include <memory>
#include <tuple>
#include <type_traits>
#include <vector>

#include <napi.h>

#include "debug.hpp"
#include "misc.hpp"

/// @brief 以下模板用于将C++的本地数据类型转换成可以node-addon-api中使用的数据类型。方便后续编写同意的
///   函数调用接口。
template <typename T > struct convertType{  
    static_assert( std::is_same<T , T >::value , "未定义类型 T 的转换规则，请补充 convertType 特化");
    using type = T;
};

template<> struct convertType< char >{ using type = Napi::Number; };
template<> struct convertType< uint8_t >{ using type = Napi::Number; };
template<> struct convertType< short >{ using type = Napi::Number; };
template<> struct convertType< uint16_t >{ using type = Napi::Number; };
template<> struct convertType< int >{ using type = Napi::Number; };
template<> struct convertType< long >{ using type = Napi::Number; };
template<> struct convertType< long long >{ using type = Napi::Number; };
template<> struct convertType< unsigned long long >{ using type = Napi::Number; };
template<> struct convertType< uint32_t >{ using type = Napi::Number; };
template<> struct convertType< float >{ using type = Napi::Number; };
template<> struct convertType< double >{ using type = Napi::Number; }; 
template<> struct convertType< bool >{ using type = Napi::Boolean; };
template<> struct convertType< std::string >{ using type = Napi::String; };
template<> struct convertType< char * >{ using type = Napi::String; };
template<typename T > struct convertType< std::vector< T > > { using type = Napi::Array; };


/// @brief 解析tuple的元素，并且根据tuple元素的具体内容生成可以在node-addon-api中使用的数据类型。通过模板递归展开的方式，
///    同意数据转换和数据对象生成接口。
/// @tparam I  [ I ], 传递参数的数量
/// @tparam Tuple[ I ]，包覆参数的元组
template< size_t I, typename Tuple >
struct tupleUnwrapper {
    /**
     * @brief 针对std::tuple进行解包。处理成napi_valaue的向量方式。而std::vector< napi_value>是可以作为传递回
     *  给nodejs的变量的。通过这个方法能够将已经通过convertType模板类映射的数据类型对应到napi_value中。统一C++调
     *  用nodejs的接口。
     * @param env[ I ], js运行环境对象
     * @param t[ I ]，参数表
     * @param params[ O ], 参数转换结果
     */
    static void unwrap(Napi::Env env, const Tuple& t, std::vector<napi_value>& params) {
        using elemType = typename std::tuple_element<I-1, Tuple>::type;
        using elemType1 = typename std::remove_cv< elemType>::type;
        using elemType2 = typename std::remove_reference< elemType1 >::type;
        
        using napiType = typename convertType<elemType2>::type;
        try{
            auto d = std::get<I-1>(t);
            params[ I - 1 ] = napiType::New(env, d );
            if( params[ I - 1 ].IsArray() ){
                using aryEleType = decltype( d[ 0 ] );
                for( size_t i = 0; i < d.size(); i ++  ){
                    params[ I - 1 ].Set( i , convertType<aryEleType>::type::New( env , d[ i ]));
                }
            }
        }catch( Napi::Error& e ){
            ERROR( e.what() );
            throw;
        }

        tupleUnwrapper< I-1, Tuple >::unwrap(env, t, params);
    }
};

template<typename Tuple>
struct tupleUnwrapper<0, Tuple> {
    static void unwrap(Napi::Env, const Tuple&, std::vector<napi_value>&) {}
};

template< typename T >
struct stFromNapiData{
    static T fromNapiData( const Napi::Value& value ){
        static_assert( std::is_same<T,T>:value , "" );
        return {};
    }
};

template<>
struct stFromNapiData< bool >
{
    static bool fromNapiData( const Napi::Value& value ){
        if( value.IsBoolean() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        return value.ToBoolean();
    }
};

template<>
struct stFromNapiData< char >
{
    static char fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        int32_t ret = (int32_t)value.ToNumber().Int32Value();
        return ret;
    }
};

template<>
struct stFromNapiData< unsigned char >
{
    static unsigned char fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        uint32_t ret = value.ToNumber().Uint32Value();
        return ret;
    }
};

template<>
struct stFromNapiData< short >
{
    static short fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        int32_t ret = value.ToNumber().Int32Value();
        return ret;
    }
};


template<>
struct stFromNapiData< unsigned short >
{
    static unsigned short fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        uint32_t ret = value.ToNumber().Uint32Value();
        return ret;
    }
};

template<>
struct stFromNapiData< unsigned int >
{
    static unsigned int fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        uint32_t ret = value.ToNumber().Uint32Value();
        return ret;
    }
};

template<>
struct stFromNapiData< int >
{
    static int fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        int ret = value.ToNumber().Int32Value();
        return ret;
    }
};

template<>
struct stFromNapiData< long long >
{
    static long long fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        int64_t ret = value.ToNumber().Int64Value();
        return ret;
    }
};

template<>
struct stFromNapiData< unsigned long long >
{
    static unsigned long long fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        uint64_t ret = value.ToNumber().As<uint64_t>();
        return ret;
    }
};

template<>
struct stFromNapiData< float >
{
    static float fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        float ret = value.ToNumber().As<float>();
        return ret;
    }
};

template<>
struct stFromNapiData< double >
{
    static double fromNapiData( const Napi::Value& value ){
        if( value.IsNumber() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        double ret = value.ToNumber().As<double>();
        return ret;
    }
};

template<>
struct stFromNapiData< std::string >
{
    static std::string fromNapiData( const Napi::Value& value ){
        if( value.IsString() == false ){
            throw std::runtime_error( "数据类型不匹配" );
        }
        std::string ret = value.ToString().Utf8Value();
        return ret;
    }
};
/// @brief 实际的调用对象接口。在使用的时候需要从nodejs侧载入对象并且实例化，然后注册nodejs中暴露出来的方法。
///   这里的call和callAsysnc方法可以通过C++暴露给其他的脚本语言。如此便能够打通其他脚本语言调用nodejs的通道
class callJS : public Napi::ObjectWrap< callJS >
{
private:
    std::map< std::string , Napi::FunctionReference >     m_cbs__;
    static callJS                                       * p_obj__;
    std::mutex                                            m_mutex__;
public:
    static Napi::Object Init( Napi::Env env , Napi::Object exports );
    /**
     * @brief 在js进行初始化
     */
    explicit callJS(const Napi::CallbackInfo& info );
    ~callJS();
    /**
     * @brief 在JS测调用，初始化方法表。这个方法需要在js中调用，并将js侧能够提供的方法全部提交给callJS模块注册。
     * @param 实际在JS侧应该以：
     *      callJS.setCallBack( "func1" ()=>{ ... ... } )
     *    方式进行调用，注册需要注册的方法。
     */
    Napi::Value setCallBack( const Napi::CallbackInfo& info);
    /**
     * @brief 移除已经注册的js方法
     * @param 
     *     callJS.removeCallBack( var funcName )
     */
    Napi::Value removeCallBack(const Napi::CallbackInfo& info);
    /**
     * @brief 清除已经注册的方法表。这个方法会清理所有的注册内容
     */
    Napi::Value clearCallbacks( const Napi::CallbackInfo& info );
    /**
     * @brief 获取callJS对象。本方法用于其他C++模块中调用JS中的方法的时候后获取对象，然后调用call或者callAsync方法
     * @return 成功操作返回callJS对象指针
     */
    static callJS * get(){ return p_obj__; }
 
    /**
     * @brief 同步调用JS方法
     * @tparam Args
     * @param name[ I ],
     * @param args[ IO ],
     * @return 返回操作的结果。
     */
    template<typename... Args>
    Napi::Value call(const std::string& name, Args&&... args) {
        std::lock_guard<std::mutex> LCK(m_mutex__);
        auto it = m_cbs__.find(name);
        if (it == m_cbs__.end()) {
            Napi::TypeError::New(env, "找不到指定方法").ThrowAsJavaScriptException();
            MSG_1( "找不到指定方法：%s" , TRED , name.c_str() );
            debug::printCallstack();
            throw std::runtime_error( std::string( "找不到指定方法： " ) + name);
        }

        auto& func = it->second;
        if( func.IsEmpty() ){
            Napi::TypeError::New(env, "指定方法已经失效").ThrowAsJavaScriptException();
            MSG_1( "指定方法已经失效%s" , TRED , name.c_str() );
            debug::printCallstack();
            throw std::runtime_error( std::string( "找不到指定方法： " ) + name);
        }
        Napi::Env env = func.Env();
        constexpr size_t argCount = sizeof...(Args);
        
        std::vector<napi_value> params(argCount);
        std::tuple<Args...> t(std::forward<Args>(args)...);
        
        if(argCount > 0) {
            tupleUnwrapper<argCount - 1, decltype(t)>::unwrap(env, t, params);
        }

        Napi::Value ret;
        try{
            ret = func.Call(params);
        }catch( const Napi::Error& e ){
            e.ThrowAsJavaScriptException();
            ERROR_MSG( e.Message() );
            debug::printCallstack();
        }catch( ... ){
            Napi::TypeError::New(env, "不明错误").ThrowAsJavaScriptException();
            ERROR_MSG( "不明错误" );
            debug::printCallstack();
        }

        return ret;
    }

    /**
     * @brief 异步调用JS方法。
     * @tparam Args , 要传递给JS函数的参数类型表
     * @param name[ I ], JS函数在callJS模块中的注册名称
     * @param args[ IO ]，要传递给JS函数的参数表
     */
    template<typename... Args>
    Napi::Promise callAsync(
        const std::string& name,
        std::function<void(const Napi::Value&, const std::string&)> cb,
        Args&&... args)
    {
        Napi::Env env = Napi::Env::Current();
        std::lock_guard<std::mutex> lock(m_mutex__);
        
        auto it = m_cbs__.find(name);
        if (it == m_cbs__.end()) {
            Napi::Error::New(env, "Function not found: " + name).ThrowAsJavaScriptException();
            auto deferred = Napi::Promise::Deferred::New(env);
            deferred.Reject(Napi::String::New(env, "Function not found"));
            return deferred.Promise();
        }

        auto deferred = Napi::Promise::Deferred::New(env);
        
        // 创建ThreadSafeFunction
        auto tsfn = Napi::ThreadSafeFunction::New(
            env,
            Napi::Function::New(env, [](const Napi::CallbackInfo& info) {}), // 空函数
            "TSFN", // 资源名称
            0, // 无限队列
            1, // 初始线程数
            [deferred](Napi::Env env) { // 最终izer
                // 清理工作
            }
        );

        // 将参数打包到shared_ptr中，确保线程安全
        using ArgsTuple = std::tuple<typename std::decay<Args>::type...>;
        auto argsData = std::make_shared<ArgsTuple>(std::forward<Args>(args)...);
        
        // 在新线程中执行异步操作
        std::thread([tsfn, deferred, it, argsData, cb, name]() mutable {
            // 准备回调函数
            auto callback = [deferred, cb](Napi::Env env, Napi::Function jsCallback, 
                                          Napi::Value* result, const std::string* error) {
                if (error && !error->empty()) {
                    // 有错误
                    cb(Napi::Boolean::New(env, false), *error);
                    deferred.Reject(Napi::String::New(env, *error));
                } else if (result) {
                    // 有结果
                    cb(*result, "");
                    deferred.Resolve(*result);
                }
            };

            try {
                // 展开参数
                constexpr size_t argCount = std::tuple_size<ArgsTuple>::value;
                std::vector<napi_value> params(argCount);
                
                if (argCount > 0) {
                    tupleUnwrapper<argCount, ArgsTuple>::unwrap(Napi::Env::Current(), *argsData, params);
                }

                // 调用JS函数
                Napi::Value result = it->second.Call(params);
                
                // 将结果传递回主线程
                auto sharedResult = std::make_shared<Napi::Value>(std::move(result));
                napi_status status = tsfn.BlockingCall(sharedResult.get(), [callback](Napi::Env env, Napi::Function jsCallback, Napi::Value* result) {
                    callback(env, jsCallback, result, nullptr);
                });
                
                if (status != napi_ok) {
                    throw std::runtime_error("Failed to call JS function");
                }
            } catch (const Napi::Error& e) {
                std::string errorMsg = e.Message();
                tsfn.BlockingCall(&errorMsg, [callback](Napi::Env env, Napi::Function jsCallback, std::string* error) {
                    callback(env, jsCallback, nullptr, error);
                });
            } catch (const std::exception& e) {
                std::string errorMsg = e.what();
                tsfn.BlockingCall(&errorMsg, [callback](Napi::Env env, Napi::Function jsCallback, std::string* error) {
                    callback(env, jsCallback, nullptr, error);
                });
            } catch (...) {
                std::string errorMsg = "Unknown error";
                tsfn.BlockingCall(&errorMsg, [callback](Napi::Env env, Napi::Function jsCallback, std::string* error) {
                    callback(env, jsCallback, nullptr, error);
                });
            }
            
            // 释放ThreadSafeFunction
            tsfn.Release();
        }).detach();

        return deferred.Promise();
    }
};